/**
 * Procesadores de Lenguaje, curso 2009-2010
 * @author grupo 02
 *
 */

package interprete;

import interprete.DataCell.Ttipo;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.ListIterator;

import util.Instruction;


public class MaquinaP {

    private class StackObj{

        private Object data;
        private Ttipo tipo;

        public StackObj clone(){

            StackObj sOnew = new StackObj(null,this.tipo);
            switch(tipo){
                case FLOAT:
                    sOnew.setData(new Float((Float)data));
                    break;

                case NAT:
                case INT:
                    sOnew.setData(new Integer((Integer)data));
                    break;

                case CHAR:
                    sOnew.setData(new Character((Character)data));
                    break;

                case BOOLEAN:
                    sOnew.setData(new Boolean((Boolean)data));
                    break;

            }
          return sOnew;

        }

        public StackObj(Object data,Ttipo tipo){
            this.data = data;
            this.tipo = tipo;
        }

        public void setTipo(Ttipo tipo){
            this.tipo=tipo;
        }

        public void setData(Object data){
            this.data = data;
        }

        public String toString(){

            return data.toString();



        }

    }


    //Data structures:

    private  LinkedList pila;
    private  ArrayList prog;
    private  boolean parada;
    private  int pc;
    private  boolean traza;
    private  MemoryManager MM;


    /*
     * Devuelve el tipo de la expresion recibida por parametro, para almacenarlo
     * junto con el valor.
     */
    private StackObj expType(String exp)throws ReadException{
        if(exp.matches("\\'.\\'")){
            //char
            return new StackObj(new Character(exp.charAt(1)),Ttipo.CHAR);
        }else if(exp.matches("\\-?\\d*(\\.|e|E).*")){
            //real sin exponente
            return new StackObj(new Float(exp),Ttipo.FLOAT);
        }else if(exp.matches("\\-\\d*")){
            //entero
            return new StackObj(new Integer(exp),Ttipo.INT);
        }else if(exp.matches("\\+\\d*")){
            //entero con signo "+" hay que eliminarlo antes de cambiarlo
            return new StackObj(new Integer(exp.substring(1)),Ttipo.INT);
        }else if(exp.matches("\\d*")){

            return new StackObj(new Integer(exp),Ttipo.INT);
        }else if(exp.matches("true|false")){
            return new StackObj(new Boolean(exp),Ttipo.BOOLEAN);
        }else throw new ReadException();

    }


    private void operacionPila(byte code, String field) throws OperationException{
        DataCell dc=null;
        StackObj sO=null;
        //mascara "00001111"
        switch (code & 15) {

            case Instruction.APILA : try{
                            pila.addFirst(expType(field)) ;
                         }catch(Exception ex){
                             System.out.println(ex.getMessage());
                         }
                         break;

    
            case Instruction.APILA_DIR :
                        dc = (DataCell)MM.get(Integer.parseInt(field));
                        pila.addFirst(new StackObj(dc.data,dc.tipo) );
                        break;


            case Instruction.DESAPILA_DIR :
                        sO= (StackObj)pila.removeFirst();
                        MM.put(Integer.parseInt(field),new DataCell(sO.data,sO.tipo));
                        break;


            case Instruction.APILA_IND :
                        /* Apila el contenido de la posicion de memoria indicada
                         * por la cima de la pila */
                        sO= (StackObj)pila.removeFirst();
                        dc= (DataCell)MM.get((Integer)sO.data);
                        pila.addFirst(new StackObj(dc.data,dc.tipo));
                        break;

            case Instruction.DESAPILA_IND :
                        /* Guarda el contenido de la cima en la posicion de memoria
                         * indicada por la subcima de la pila*/
                        sO= (StackObj)pila.removeFirst();
                        StackObj s1 = (StackObj)pila.removeFirst();
                        MM.put((Integer)s1.data,new DataCell(sO.data,sO.tipo));
                        break;

            case Instruction.MUEVE:
                        /* Movimento de bloques completos de memoria.
                         * Cima de la pila, direccion origen, subcima de la pila
                         * direccion destino y como parametro de la instruccion
                         * tamaño a desplazar */
                        sO= (StackObj)pila.removeFirst();
                        StackObj destino = (StackObj)pila.removeFirst();
                        int size = Integer.parseInt(field);
                                                
                        for (int i=0;i < size;i++ ){
                            MM.put((Integer)destino.data+i, MM.get((Integer)sO.data+i));
                            MM.put((Integer)sO.data+i,null);
                        }
                        break;

            case Instruction.COPIA:
                        sO= ((StackObj)pila.getFirst()).clone();
                        pila.addFirst(sO);
                        break;

            case Instruction.DESAPILA:
                        pila.removeFirst();
                        break;
                        
            case Instruction.FLIP:
                /* Intercambia la posicion de los dos elementos al inicio de la
                 * pila.
                 */
                        sO = (StackObj) pila.removeFirst();
                        pila.add(1,sO);
                        break;
             default: throw new OpNotSupportedException(code);

        }
    }

    private void operacionMisc(byte code, String field) throws OperationException{

      try{
        //applied mask "00001111"
        switch (code & 15){

            case Instruction.CFLOAT: {
                       StackObj sO=(StackObj)pila.getFirst();
                       float value;
                       if(sO.tipo == Ttipo.INT || sO.tipo==Ttipo.NAT){
                           value = ((Integer)sO.data);
                           sO.tipo=Ttipo.FLOAT;
                           sO.data=new Float(value);
                       }else if(sO.tipo == Ttipo.CHAR){
                           value = (float)(Character)sO.data;
                           sO.tipo=Ttipo.FLOAT;
                           sO.data=value;
                       }
                       }break;
            
            case Instruction.CINT: {
                       StackObj sO=(StackObj)pila.getFirst();
                       int value;
                       if(sO.tipo == Ttipo.FLOAT){
                           value = ((Float)sO.data).intValue();
                           sO.tipo=Ttipo.INT;
                           sO.data=new Integer(value);
                       }else if(sO.tipo ==Ttipo.NAT)
                           sO.tipo=Ttipo.INT;
                       else if(sO.tipo == Ttipo.CHAR){
                           value = (int)(Character)sO.data;
                           sO.tipo=Ttipo.INT;
                           sO.data=value;
                       }
                       }break;
                
            
            case Instruction.CNAT: {
                       StackObj sO=(StackObj)pila.getFirst();
                       int value;
                       value = (int)(Character)sO.data;
                       sO.tipo=Ttipo.NAT;
                       sO.data=value;

            
            
                        }
                break;
            case Instruction.CCHAR:{
                       StackObj sO=(StackObj)pila.getFirst();
                       char value;
                       value = (char)((Integer)sO.data).intValue();
                       sO.tipo=Ttipo.CHAR;
                       sO.data=value;

                        }
                break;

            case Instruction.LEER: leer() ;break;

            case Instruction.ESCR: escribir();break;

            case Instruction.NEW:
                        int dir= MM.allocate(Integer.parseInt(field));
                                   //apila la direccion que el gestor de memoria a
                                   //alojado para los datos pedidos. 
                                   pila.addFirst(new StackObj(new Integer(dir),Ttipo.INT));

                        break;

            case Instruction.DISPOSE:
                        int address =(Integer) ((StackObj)pila.removeFirst()).data;
                        MM.deallocate(Integer.parseInt(field),address);
                        break;

            case Instruction.SEG:  MM.resize(Integer.parseInt(field));
                                   break;
                
            case Instruction.STOP: parada = true; break;

            default: throw new OpNotSupportedException(code);

        }

      }
      catch(HeapException he){
          he.printStackTrace();
      }

    }

    private void leer(){
        boolean again = true;
        StackObj sO;
        while(again) {
            System.out.println("input:");
            try {
                String str = (new BufferedReader(new InputStreamReader(System.in))).readLine();
                sO= expType(str);
                again=false;
                //introducir en la pila el elemento leido
                pila.addFirst(sO);
            } catch (Exception ex) {
                System.out.println(ex.getMessage());
            }
        }

    }

    private void escribir(){
        StackObj sO =(StackObj) pila.getFirst();
        switch (sO.tipo){
            case INT:
                System.out.println((Integer)sO.data);break;
            case NAT:
                System.out.println((Integer)sO.data);break;
            case FLOAT:
                System.out.println((Float)sO.data);break;
            case CHAR:
                System.out.println((Character)sO.data);break;
            case BOOLEAN:
                System.out.println((Boolean)sO.data);break;
        }
    }


    /*
     * resultType
     * calcula el tipo de operacion aritmetica en funcion de los tipos de los operadores
     */

    private Ttipo resultType(Ttipo oper1, Ttipo oper2){
            if(oper1 == Ttipo.FLOAT || oper2 ==Ttipo.FLOAT){
                return Ttipo.FLOAT;
            }else if (oper1 == Ttipo.INT || oper2 ==Ttipo.INT)
                return Ttipo.INT;
            else return Ttipo.NAT;
    }

    private Ttipo resultType(Ttipo oper){
            if(oper == Ttipo.FLOAT) return Ttipo.FLOAT;
            else return Ttipo.INT;

    }

    private void operacionArit(byte code) throws OperationException {

        StackObj op1,total;
        StackObj op2 = (StackObj)pila.removeFirst();
        try{
        //Mascara de bits "00001111"

        switch (code & 15){

            case Instruction.SUMA:
                op1 = (StackObj)pila.removeFirst();
                pila.addFirst( suma(op1,op2));
                break;

            case Instruction.RESTA:
                op1 = (StackObj)pila.removeFirst();
                pila.addFirst( resta(op1,op2));
                break;

            case Instruction.MULT:
                op1 = (StackObj)pila.removeFirst();
                pila.addFirst( mul(op1,op2));
                break;

            case Instruction.DIV:
                op1 = (StackObj)pila.removeFirst();
                pila.addFirst( div(op1,op2));
                break;
                

            case Instruction.MOD:
                op1 = (StackObj)pila.removeFirst();
                pila.addFirst( mod(op1,op2));
                break;


            case Instruction.DESPD:
                op1 = (StackObj)pila.removeFirst();
                pila.addFirst( despD(op1,op2));
                break;


            case Instruction.DESPI:
                op1 = (StackObj)pila.removeFirst();
                pila.addFirst( despI(op1,op2));
                break;

            case Instruction.SIGNO:
                pila.addFirst( signo(op2));
                break;
            case Instruction.ABS:
                pila.addFirst( absoluto(op2));
                break;

            default: throw new OpNotSupportedException(code);


        }


        }catch(Exception ex){
            System.out.println(ex.getMessage());
          }


    
        
        

    }

    private StackObj suma(StackObj op1,StackObj op2){
        Ttipo result=resultType(op1.tipo,op2.tipo);
        switch(result){

            case FLOAT: if(op1.tipo ==Ttipo.INT || op1.tipo==Ttipo.NAT)
                            return new StackObj(new Float((Integer)op1.data + (Float)op2.data),result);
                        else if(op2.tipo ==Ttipo.INT || op2.tipo==Ttipo.NAT)
                            return new StackObj(new Float((Float)op1.data + (Integer)op2.data),result);
                        else
                            return new StackObj(new Float((Float)op1.data + (Float)op2.data),result);

            case INT: return new StackObj(new Integer((Integer)op1.data + (Integer)op2.data),result);

            case NAT: return new StackObj(new Integer((Integer)op1.data + (Integer)op2.data),result);
        }
        return null;
    }

    private StackObj resta(StackObj op1,StackObj op2) throws OperationException{
        Ttipo result=resultType(op1.tipo,op2.tipo);
        switch(result){

            case FLOAT: if(op1.tipo ==Ttipo.INT || op1.tipo==Ttipo.NAT)
                            return new StackObj(new Float((Integer)op1.data - (Float)op2.data),result);
                        else if(op2.tipo ==Ttipo.INT || op2.tipo==Ttipo.NAT)
                            return new StackObj(new Float((Float)op1.data - (Integer)op2.data),result);
                        else
                            return new StackObj(new Float((Float)op1.data - (Float)op2.data),result);
                        


            case INT: return new StackObj(new Integer((Integer)op1.data - (Integer)op2.data),result);

            case NAT: Integer cal = new Integer((Integer)op1.data - (Integer)op2.data);
                      if(cal<0) throw new OperationException("resultado negativo, no soportado por tipo natural");
                      else return new StackObj(new Integer((Integer)op1.data - (Integer)op2.data),result);
        }
        return null;
    }

    private StackObj mul(StackObj op1,StackObj op2){
        Ttipo result=resultType(op1.tipo,op2.tipo);
        switch(result){

            case FLOAT: if(op1.tipo ==Ttipo.INT || op1.tipo==Ttipo.NAT)
                            return new StackObj(new Float((Integer)op1.data * (Float)op2.data),result);
                        else if(op2.tipo ==Ttipo.INT || op2.tipo==Ttipo.NAT)
                            return new StackObj(new Float((Float)op1.data * (Integer)op2.data),result);
                        else
                            return new StackObj(new Float((Float)op1.data * (Float)op2.data),result);

            case INT: return new StackObj(new Integer((Integer)op1.data * (Integer)op2.data),result);

            case NAT: return new StackObj(new Integer((Integer)op1.data * (Integer)op2.data),result);
            
        }
        return null;
    }

    private void no_cero(StackObj op2) throws ZeroException{
        
        if(op2.tipo == Ttipo.FLOAT){
            if ((Float)op2.data == 0) throw new ZeroException();
        }else
            if ((Integer)op2.data == 0) throw new ZeroException();             

    }

    private StackObj div(StackObj op1,StackObj op2) throws ZeroException{

        no_cero(op2);
        Ttipo result=resultType(op1.tipo,op2.tipo);
        //Java identifica el tipo de division (entera, real) segun los tipos de los operandos:
        switch(result){
            case FLOAT: if(op1.tipo ==Ttipo.INT || op1.tipo==Ttipo.NAT)
                            return new StackObj(new Float((Integer)op1.data / (Float)op2.data),result);
                        else if(op2.tipo ==Ttipo.INT || op2.tipo==Ttipo.NAT)
                            return new StackObj(new Float((Float)op1.data / (Integer)op2.data),result);
                        else
                            return new StackObj(new Float((Float)op1.data / (Float)op2.data),result);

            case INT: return new StackObj(new Integer((Integer)op1.data / (Integer)op2.data),result);

            case NAT: return new StackObj(new Integer((Integer)op1.data / (Integer)op2.data),result);
        }
        return null;
    }

    private StackObj mod(StackObj op1,StackObj op2) throws ZeroException{
        no_cero(op2);
        Ttipo result=op1.tipo;
        switch(result){

            case INT: return new StackObj(new Integer((Integer)op1.data % (Integer)op2.data),result);

            case NAT: return new StackObj(new Integer((Integer)op1.data % (Integer)op2.data),result);
        }
        return null;
    }

    private StackObj despI(StackObj op1, StackObj op2){
        return new StackObj(new Integer( (Integer)op1.data << (Integer)op2.data),Ttipo.NAT);
    }

    private StackObj despD(StackObj op1, StackObj op2){
        return new StackObj(new Integer( (Integer)op1.data >> (Integer)op2.data),Ttipo.NAT);
    }

    private StackObj signo(StackObj op1){
        Ttipo result=resultType(op1.tipo);
        switch(result){

            case FLOAT: return new StackObj(new Float(-(Float)op1.data),result);

            //si el tipo es natural tambien se opera en este case,
            //asignado tipo entero al resultado:
            case INT: return new StackObj(new Integer(-(Integer)op1.data),result);
        }
        return null;

    }

    private StackObj absoluto(StackObj op1){
        Ttipo result=resultType(op1.tipo);
        switch(op1.tipo){

            case FLOAT: return new StackObj(new Float(Math.abs((Float)op1.data)),result);

            //si el tipo es natural tambien se opera en este case,
            //asignado tipo entero al resultado:
            case NAT:
            case INT: return new StackObj(new Integer(Math.abs((Integer)op1.data)),Ttipo.NAT);
        }
        return null;

    }


    private void operacionLogi(byte code) throws OperationException {

        StackObj op1,total;
        StackObj op2 = (StackObj)pila.removeFirst();

        //Mascara de bits "00001111"
        switch (code & 15){
            case Instruction.AND:
                    op1 = (StackObj)pila.removeFirst();
                    pila.addFirst(and(op1,op2));
                    break;

            case Instruction.OR:
                    op1 = (StackObj)pila.removeFirst();
                    pila.addFirst(or(op1,op2));
                    break;

            case Instruction.MENOR:
                    op1 = (StackObj)pila.removeFirst();
                    pila.addFirst(menor(op1,op2));
                    break;

            case Instruction.MAYOR:
                    op1 = (StackObj)pila.removeFirst();
                    pila.addFirst(mayor(op1,op2));
                    break;

            case Instruction.MENIG:
                    op1 = (StackObj)pila.removeFirst();
                    pila.addFirst(menig(op1,op2));
                    break;

            case Instruction.MAYIG:
                    op1 = (StackObj)pila.removeFirst();
                    pila.addFirst(mayig(op1,op2));
                    break;

            case Instruction.DIST:
                    op1 = (StackObj)pila.removeFirst();
                    pila.addFirst(dist(op1,op2));
                    break;

            case Instruction.IGUAL:
                    op1 = (StackObj)pila.removeFirst();
                    pila.addFirst(igual(op1,op2));
                    break;

            case Instruction.NEG:
                    pila.addFirst(neg(op2));
                    break;

            default:  throw new OpNotSupportedException(code);
        }


    }

    private StackObj and(StackObj op1,StackObj op2){

        return new StackObj((Boolean)op1.data && (Boolean)op2.data,Ttipo.BOOLEAN);

    }

    private StackObj or(StackObj op1,StackObj op2){
        return new StackObj((Boolean)op1.data || (Boolean)op2.data,Ttipo.BOOLEAN);

    }

    private int bool_to_int(Boolean b){
        if (b.booleanValue() == true) return 1;
        else return 0;

    }

    private StackObj menor(StackObj op1,StackObj op2){
        switch(op1.tipo){
            case BOOLEAN :
                return new StackObj(new Boolean (bool_to_int((Boolean)op1.data) < bool_to_int((Boolean)op2.data)),Ttipo.BOOLEAN);
            case CHAR :
                return new StackObj(new Boolean ((int)(Character)op1.data < (int)(Character)op2.data),Ttipo.BOOLEAN);

            case FLOAT: { switch(op2.tipo){
                            case FLOAT: return new StackObj(new Boolean((Float)op1.data < (Float)op2.data),Ttipo.BOOLEAN);
                            case NAT:
                            case INT: return new StackObj(new Boolean((Float)op1.data < (Integer)op2.data),Ttipo.BOOLEAN);
                            }
                }
            case NAT:
            case INT:{  switch(op2.tipo){
                            case FLOAT: return new StackObj(new Boolean((Integer)op1.data < (Float)op2.data),Ttipo.BOOLEAN);
                            case NAT:
                            case INT: return new StackObj(new Boolean((Integer)op1.data < (Integer)op2.data),Ttipo.BOOLEAN);
                            }


            }

            default: return null;

        }
    }

    private StackObj mayor(StackObj op1,StackObj op2){
      switch(op1.tipo){
            case BOOLEAN :
                return new StackObj(new Boolean (bool_to_int((Boolean)op1.data) > bool_to_int((Boolean)op2.data)),Ttipo.BOOLEAN);
            case CHAR :
                return new StackObj(new Boolean ((int)(Character)op1.data > (int)(Character)op2.data),Ttipo.BOOLEAN);

            case FLOAT: { switch(op2.tipo){
                            case FLOAT: return new StackObj(new Boolean((Float)op1.data > (Float)op2.data),Ttipo.BOOLEAN);
                            case NAT:
                            case INT: return new StackObj(new Boolean((Float)op1.data > (Integer)op2.data),Ttipo.BOOLEAN);
                            }
                }
            case NAT:
            case INT:{  switch(op2.tipo){
                            case FLOAT: return new StackObj(new Boolean((Integer)op1.data > (Float)op2.data),Ttipo.BOOLEAN);
                            case NAT:
                            case INT: return new StackObj(new Boolean((Integer)op1.data > (Integer)op2.data),Ttipo.BOOLEAN);
                            }


            }

            default: return null;

        }


    }

    private StackObj igual(StackObj op1,StackObj op2){
  switch(op1.tipo){
            case BOOLEAN :
                return new StackObj(new Boolean (bool_to_int((Boolean)op1.data) == bool_to_int((Boolean)op2.data)),Ttipo.BOOLEAN);
            case CHAR :
                return new StackObj(new Boolean ((int)(Character)op1.data == (int)(Character)op2.data),Ttipo.BOOLEAN);

            case FLOAT: { switch(op2.tipo){
                            case FLOAT: return new StackObj(new Boolean((Float)op1.data == (Float)op2.data),Ttipo.BOOLEAN);
                            case NAT:
                            case INT: return new StackObj(new Boolean((Float)op1.data == (float)(Integer)op2.data),Ttipo.BOOLEAN);
                            }
                }
            case NAT:
            case INT:{  switch(op2.tipo){
                            case FLOAT: return new StackObj(new Boolean((float)(Integer)op1.data == (Float)op2.data),Ttipo.BOOLEAN);
                            case NAT:
                            case INT: return new StackObj(new Boolean((Integer)op1.data == (Integer)op2.data),Ttipo.BOOLEAN);
                            }


            }

            default: return null;

        }

    }

    private StackObj dist(StackObj op1,StackObj op2){
  switch(op1.tipo){
            case BOOLEAN :
                return new StackObj(new Boolean (bool_to_int((Boolean)op1.data) != bool_to_int((Boolean)op2.data)),Ttipo.BOOLEAN);
            case CHAR :
                return new StackObj(new Boolean ((int)(Character)op1.data != (int)(Character)op2.data),Ttipo.BOOLEAN);

            case FLOAT: { switch(op2.tipo){
                            case FLOAT: return new StackObj(new Boolean((Float)op1.data != (Float)op2.data),Ttipo.BOOLEAN);
                            case NAT:
                            case INT: return new StackObj(new Boolean((Float)op1.data != (float)(Integer)op2.data),Ttipo.BOOLEAN);
                            }
                }
            case NAT:
            case INT:{  switch(op2.tipo){
                            case FLOAT: return new StackObj(new Boolean((float)(Integer)op1.data != (Float)op2.data),Ttipo.BOOLEAN);
                            case NAT:
                            case INT: return new StackObj(new Boolean((Integer)op1.data != (Integer)op2.data),Ttipo.BOOLEAN);
                            }


            }

            default: return null;

        }

    }


    private StackObj menig(StackObj op1,StackObj op2){
        switch(op1.tipo){
            case BOOLEAN :
                return new StackObj(new Boolean (bool_to_int((Boolean)op1.data) <= bool_to_int((Boolean)op2.data)),Ttipo.BOOLEAN);
            case CHAR :
                return new StackObj(new Boolean ((int)(Character)op1.data <= (int)(Character)op2.data),Ttipo.BOOLEAN);

            case FLOAT: { switch(op2.tipo){
                            case FLOAT: return new StackObj(new Boolean((Float)op1.data <= (Float)op2.data),Ttipo.BOOLEAN);
                            case NAT:
                            case INT: return new StackObj(new Boolean((Float)op1.data <= (float)(Integer)op2.data),Ttipo.BOOLEAN);
                            }
                }
            case NAT:
            case INT:{  switch(op2.tipo){
                            case FLOAT: return new StackObj(new Boolean((float)(Integer)op1.data <= (Float)op2.data),Ttipo.BOOLEAN);
                            case NAT:
                            case INT: return new StackObj(new Boolean((Integer)op1.data <= (Integer)op2.data),Ttipo.BOOLEAN);
                            }


            }

            default: return null;

        }

    }



    private StackObj mayig(StackObj op1,StackObj op2){
        switch(op1.tipo){
            case BOOLEAN :
                return new StackObj(new Boolean (bool_to_int((Boolean)op1.data) >= bool_to_int((Boolean)op2.data)),Ttipo.BOOLEAN);
            case CHAR :
                return new StackObj(new Boolean ((int)(Character)op1.data >= (int)(Character)op2.data),Ttipo.BOOLEAN);

            case FLOAT: { switch(op2.tipo){
                            case FLOAT: return new StackObj(new Boolean((Float)op1.data >= (Float)op2.data),Ttipo.BOOLEAN);
                            case NAT:
                            case INT: return new StackObj(new Boolean((Float)op1.data >= (Integer)op2.data),Ttipo.BOOLEAN);
                            }
                }
            case NAT:
            case INT:{  switch(op2.tipo){
                            case FLOAT: return new StackObj(new Boolean((Integer)op1.data >= (Float)op2.data),Ttipo.BOOLEAN);
                            case NAT:
                            case INT: return new StackObj(new Boolean((Integer)op1.data >= (Integer)op2.data),Ttipo.BOOLEAN);
                            }


            }

            default: return null;

        }

    }

    private StackObj neg(StackObj op1){
        return new StackObj(new Boolean(!((Boolean)op1.data).booleanValue()),Ttipo.BOOLEAN);
    }



       private void operacionSalt(byte code, String field) throws OperationException{
        DataCell dc=null;
        StackObj sO=null;

        //mascara "00001111"
        switch (code & 15) {

            case Instruction.IR_A :
                // Salto incondicional, Salta a la instruccion indicada por la
                // etiqueta.
                        this.pc = buscarEtq(Integer.parseInt(field));
                        break;

            
            case Instruction.IR_F :
                // Si en la cima tiene como valor "0", salta a la instrucccion
                // indicada en la etiqueta.
                        sO = (StackObj)pila.removeFirst();
                        if(sO.tipo == Ttipo.BOOLEAN){
                                if(((Boolean)sO.data) == false)
                                    this.pc = buscarEtq(Integer.parseInt(field));
                        }
                        else
                            if (((Integer)sO.data) == 0)
                              this.pc = buscarEtq(Integer.parseInt(field));
                        break;


            case Instruction.IR_V :
                // Si en la cima tiene como valor distinto a "0", salta a la instrucccion
                // indicada en la etiqueta.
                        sO = (StackObj)pila.removeFirst();
                        if(sO.tipo == Ttipo.BOOLEAN){
                                if(((Boolean)sO.data) != false)
                                    this.pc = buscarEtq(Integer.parseInt(field));
                        }
                        else
                            if (((Integer)sO.data) != 0)
                              this.pc = buscarEtq(Integer.parseInt(field));
                        break;

            
            case Instruction.IR_IND :
                // Salta a la direccion indicada en la cima de la pila.
                        sO= (StackObj)pila.removeFirst();
                        pc = (Integer)sO.data-1;
                        break;


             default: throw new OpNotSupportedException(code);

        }
    }


    private int buscarEtq(int etiqueta){
        //Dada la implementacion se puede indexar directamente la instruccion
        // sin necesida de realizar una busqueda, descontando uno pues las instrucciones
        // en la maquina comienzan desde 
        return etiqueta-1;
    }


    private void printStack(){
        ListIterator it = pila.listIterator();
        while (it.hasNext())
            System.out.println(" | " + (it.next()).toString()+  " | ");


    }

    private void printMemory(){

     System.out.println(MM.toString());

    }

    /**
     * Shows the Virtual Machine State
     */

    private void state_wait(Instruction i){
    System.out.println("Program Stack:");
    printStack();
    System.out.println("Program Memory:");
    printMemory();
    System.out.println("Program counter: " + pc );
    System.out.println("Next instruction: " + i.toString());
    System.out.println("Press enter to continue");
        try {
            InputStreamReader readIn = new InputStreamReader(System.in);
            readIn.read();
        } catch (Exception ex) {
            System.out.println(ex.getMessage());
        }


    }


    public void inicia(){

        pila = new LinkedList();
        prog = new ArrayList();
        MM = new MemoryManager(255,50);
        parada = false;
        pc = 0;
        traza = false;
       

    }

  
    
    public void loadInstruction (Instruction i){
        prog.add(i);
    }

    public void inicia(boolean traza) {
        inicia();
        this.traza = traza;
    }

    public void run(){
        //programa a ejecutar:
        System.out.println(prog.toString());

        //Ejecucion del codigo
        while (!parada){
            Instruction i = (Instruction)prog.get(pc);
            if (traza)
                state_wait(i);
            pc++;
            executeInstruction(i);
        }
        System.out.println("Fin de programa");

    }

    public String printProg(){
      String program = new String();
        while (!prog.isEmpty())
            program +=((Instruction)prog.remove(0)).toString()+"\n";
        return program;
    }

    public void executeInstruction(Instruction i){

      try{
        //Mascara de bits : "01110000"
        int opCode = i.getCode() & 112;

        switch (opCode){

            //instruccion de fin
                //000000
            case Instruction.OPPILA : operacionPila(i.getCode(),i.getField());break;
                //00010000
            case Instruction.OPARIT : operacionArit(i.getCode());break;
                //00100000
            case Instruction.OPLOG: operacionLogi(i.getCode());break;
                //00110000
            case Instruction.OPSALT: operacionSalt(i.getCode(),i.getField());break;
                //01110000
            case Instruction.OPMISC : operacionMisc(i.getCode(),i.getField());break;

            default: throw new OpNotSupportedException(opCode);


        }

        }catch(Exception ex){
            System.out.println(ex.getMessage());
          } 
    }



}
